// Copyright (c) 2018, Cogent Core. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package parse does the parsing stage after lexing
package parser

//go:generate core generate

import (
	"fmt"
	"io"

	"cogentcore.org/core/base/indent"
	"cogentcore.org/core/text/parse/lexer"
	"cogentcore.org/core/text/parse/syms"
	"cogentcore.org/core/text/textpos"
	"cogentcore.org/core/tree"
)

// AST is a node in the abstract syntax tree generated by the parsing step
// the name of the node (from tree.NodeBase) is the type of the element
// (e.g., expr, stmt, etc)
// These nodes are generated by the parser.Rule's by matching tokens
type AST struct {
	tree.NodeBase

	// region in source lexical tokens corresponding to this AST node -- Ch = index in lex lines
	TokReg textpos.Region `set:"-"`

	// region in source file corresponding to this AST node
	SrcReg textpos.Region `set:"-"`

	// source code corresponding to this AST node
	Src string `set:"-"`

	// stack of symbols created for this node
	Syms syms.SymStack `set:"-"`
}

func (ast *AST) Destroy() {
	ast.Syms.ClearAST()
	ast.Syms = nil
	ast.NodeBase.Destroy()
}

// ChildAST returns the Child at given index as an AST.
// Will panic if index is invalid -- use Try if unsure.
func (ast *AST) ChildAST(idx int) *AST {
	return ast.Child(idx).(*AST)
}

// ChildASTTry returns the Child at given index as an AST,
// nil if not in range.
func (ast *AST) ChildASTTry(idx int) *AST {
	if ast == nil {
		return nil
	}
	if idx < 0 || idx >= len(ast.Children) {
		return nil
	}
	return ast.Child(idx).(*AST)
}

// ParentAST returns the Parent as an AST.
func (ast *AST) ParentAST() *AST {
	if ast.Parent == nil {
		return nil
	}
	pn := ast.Parent.AsTree().This
	if pn == nil {
		return nil
	}
	return pn.(*AST)
}

// NextAST returns the next node in the AST tree, or nil if none
func (ast *AST) NextAST() *AST {
	nxti := tree.Next(ast)
	if nxti == nil {
		return nil
	}
	return nxti.(*AST)
}

// NextSiblingAST returns the next sibling node in the AST tree, or nil if none
func (ast *AST) NextSiblingAST() *AST {
	nxti := tree.NextSibling(ast)
	if nxti == nil {
		return nil
	}
	return nxti.(*AST)
}

// PrevAST returns the previous node in the AST tree, or nil if none
func (ast *AST) PrevAST() *AST {
	nxti := tree.Previous(ast)
	if nxti == nil {
		return nil
	}
	return nxti.(*AST)
}

// SetTokReg sets the token region for this rule to given region
func (ast *AST) SetTokReg(reg textpos.Region, src *lexer.File) {
	ast.TokReg = reg
	ast.SrcReg = src.TokenSrcReg(ast.TokReg)
	ast.Src = src.RegSrc(ast.SrcReg)
}

// SetTokRegEnd updates the ending token region to given position --
// token regions are typically over-extended and get narrowed as tokens actually match
func (ast *AST) SetTokRegEnd(pos textpos.Pos, src *lexer.File) {
	ast.TokReg.End = pos
	ast.SrcReg = src.TokenSrcReg(ast.TokReg)
	ast.Src = src.RegSrc(ast.SrcReg)
}

// WriteTree writes the AST tree data to the writer -- not attempting to re-render
// source code -- just for debugging etc
func (ast *AST) WriteTree(out io.Writer, depth int) {
	ind := indent.Tabs(depth)
	fmt.Fprintf(out, "%v%v: %v\n", ind, ast.Name, ast.Src)
	for _, k := range ast.Children {
		ai := k.(*AST)
		ai.WriteTree(out, depth+1)
	}
}
